package com.letu.core.utils;

import com.letu.annotation.annotation.LanguageConvert.*;
import com.letu.annotation.base.LanguageTypeEnum;
import com.letu.annotation.base.SupplementFieldEnum;
import com.letu.core.model.EntityLanguageStructure;
import com.letu.core.model.FieldLanguageInfo;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ ClassName EntityLanguageFieldConvertUtils
 * @ Description TODO
 * @ Author yuwx
 * @ Date 2019/4/11 13:28
 * @ Version 1.0
 **/
@Slf4j
public class EntityLanguageFieldConvertUtils {

    //类名
    private static Map<String, Map<SupplementFieldEnum, List<FieldLanguageInfo>>> entityLanguageFieldPairMap = new ConcurrentHashMap<>(32);
    //类名
    private static Map<String, EntityLanguageStructure> entityLanguageStructureHashMap = new ConcurrentHashMap<>(32);

    /**
     * @ Description 多语言字段补全，比如中文环境：name的值为空，则将enName的值补全到name；如果enName的值为空，则不补全
     * @ Date 14:33 2019/4/19
     * @ param vo
     * @ param languageEnum
     * @ return void
     * @ Author yuwx
     **/
    public static <T> void convert(T vo, LanguageTypeEnum languageTypeEnum) {

        if (!validate(vo) || null == languageTypeEnum || vo.getClass().isPrimitive() || vo.getClass().isArray()) {
            return;
        }
        if (!vo.getClass().isAnnotationPresent(LanguageEntityConvert.class)) {
            return;
        }
        resolveClass(vo.getClass());
        convertPrimitiveField(vo, languageTypeEnum);
        convertReferenceField(vo, languageTypeEnum);
        convertCollectionField(vo, languageTypeEnum);
        convertMapField(vo, languageTypeEnum);
    }

    /**
     * @ Description 根据语言环境，将多个环境的值，填充到标注@LanguagePrimary的字段
     * 例如，name和enName,  name字段是@LanguagePrimary， 中文环境将name填充到name,英文环境将enName填充到name
     * @ Date 14:29 2019/4/19
     * @ param vo
     * @ param languageEnum
     * @ return void
     * @ Author yuwx
     **/
    public static <T> void convertPrimary(T vo, LanguageTypeEnum languageTypeEnum) {

        if (!validate(vo) || null == languageTypeEnum || vo.getClass().isPrimitive() || vo.getClass().isArray()) {
            return;
        }
        if (!vo.getClass().isAnnotationPresent(LanguageEntityConvert.class)) {
            return;
        }
        try {
            resolveClass(vo.getClass());
            convertPrimaryField(vo, languageTypeEnum);
            convertPrimaryReferenceField(vo, languageTypeEnum);
            convertPrimaryCollectionField(vo, languageTypeEnum);
        } catch (Exception e) {
            log.error("中英文转换异常", e);
        }

    }

    private static <T> void convertPrimaryField(T vo, LanguageTypeEnum languageTypeEnum) {
        Map<SupplementFieldEnum, List<FieldLanguageInfo>> supplementFieldMap = entityLanguageFieldPairMap.get(vo.getClass().getCanonicalName());
        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<FieldLanguageInfo> primitiveFieldList = entityLanguageStructure.getPrimitiveFieldList();
        FieldLanguageInfo currentFieldLanguageInfo;
        Field currentField;
        for (int i = 0; i < primitiveFieldList.size(); i++) {
            currentFieldLanguageInfo = primitiveFieldList.get(i);
            currentField = currentFieldLanguageInfo.getField();
            if (currentField.isAnnotationPresent(LanguagePrimary.class)) {
                List<FieldLanguageInfo> fieldLanguageInfoPairList = supplementFieldMap.get(currentFieldLanguageInfo.getSupplementFieldEnum());
                //设置临时变量，用来处理需要填充主字段的
                //逻辑：循环多语言字段列表，如果当前字段值非空，赋值给给临时字段；如果当前字段值非空，且是符合当前语言环境，终止循环，给主字段赋值；如果主字段是空，则一直循环，取其他语言环境的字段值
                Object supplementValue = null;
                for (int j = 0; j < fieldLanguageInfoPairList.size(); j++) {
                    FieldLanguageInfo supplementField = fieldLanguageInfoPairList.get(j);
                    Object tempSupplementValue = ClassUtils.getFieldValue(vo, supplementField.getField());
                    if (isNotNull(tempSupplementValue)) {
                        supplementValue = tempSupplementValue;
                        if (languageTypeEnum.equals(supplementField.getLanguageTypeEnum())) {
                            break;
                        }
                    }

                }
                if (isNotNull(supplementValue)) {
                    currentField.setAccessible(true);
                    ClassUtils.setFieldValue(vo, currentField, supplementValue);
                }
            }
        }
    }

    private static <T> void convertPrimaryReferenceField(T vo, LanguageTypeEnum languageTypeEnum) throws IllegalAccessException {
        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<Field> referenceFieldList = entityLanguageStructure.getReferenceFieldList();
        Field concurrentReferenceField;
        for (int i = 0; i < referenceFieldList.size(); i++) {
            concurrentReferenceField = referenceFieldList.get(i);
            Object referenceValue = concurrentReferenceField.get(vo);
            if (null != referenceValue) {
                convertPrimary(referenceValue, languageTypeEnum);
            }
        }
    }

    private static <T> void convertPrimaryCollectionField(T vo, LanguageTypeEnum languageTypeEnum) throws IllegalAccessException {

        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<Field> collectionFieldList = entityLanguageStructure.getCollectionFieldList();
        Field concurrentCollectionField;
        for (int i = 0; i < collectionFieldList.size(); i++) {
            concurrentCollectionField = collectionFieldList.get(i);
            Collection collectionValue = (Collection) concurrentCollectionField.get(vo);
            if (null == collectionValue) {
                continue;
            }
            Iterator iterator = collectionValue.iterator();
            while (iterator.hasNext()) {
                convertPrimary(iterator.next(), languageTypeEnum);
            }

        }
    }

    private static <T> void convertPrimitiveField(T vo, LanguageTypeEnum languageTypeEnum) {
        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        Map<SupplementFieldEnum, List<FieldLanguageInfo>> supplementFieldMap = entityLanguageFieldPairMap.get(vo.getClass().getCanonicalName());
        List<FieldLanguageInfo> fieldLanguageInfoList = entityLanguageStructure.getPrimitiveFieldList();
        FieldLanguageInfo concurrentFieldLanguageInfo;
        for (int i = 0; i < fieldLanguageInfoList.size(); i++) {
            concurrentFieldLanguageInfo = fieldLanguageInfoList.get(i);
            Object concurrentValue = ClassUtils.getFieldValue(vo, concurrentFieldLanguageInfo.getField());
            if (isNotNull(concurrentValue)) {
                continue;
            }

            if (languageTypeEnum.equals(concurrentFieldLanguageInfo.getLanguageTypeEnum())) {
                List<FieldLanguageInfo> fieldLanguageInfoPairList = supplementFieldMap.get(concurrentFieldLanguageInfo.getSupplementFieldEnum());
                for (int j = 0; j < fieldLanguageInfoPairList.size(); j++) {
                    FieldLanguageInfo supplementField = fieldLanguageInfoPairList.get(j);
                    if (supplementField.getField().getName().equals(concurrentFieldLanguageInfo.getField().getName())) {
                        continue;
                    }
                    Object supplementValue = ClassUtils.getFieldValue(vo, supplementField.getField());
                    if (null == supplementValue) {
                        continue;
                    }

                    concurrentFieldLanguageInfo.getField().setAccessible(true);
                    ClassUtils.setFieldValue(vo, concurrentFieldLanguageInfo.getField(), supplementValue);

                }
            }
        }
    }

    private static boolean isNotNull(Object object) {
        if (null == object) {
            return false;
        }
        if (object.getClass().equals(String.class) && StringUtils.isBlank(object.toString())) {
            return false;
        }
        return true;
    }

    private static <T> void convertReferenceField(T vo, LanguageTypeEnum languageTypeEnum) {
        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<Field> referenceFieldList = entityLanguageStructure.getReferenceFieldList();
        Field concurrentReferenceField;
        for (int i = 0; i < referenceFieldList.size(); i++) {
            concurrentReferenceField = referenceFieldList.get(i);
            Object referenceValue = ClassUtils.getFieldValue(vo, concurrentReferenceField);
            if (null != referenceValue) {
                convert(referenceValue, languageTypeEnum);
            }
        }
    }

    private static <T> void convertCollectionField(T vo, LanguageTypeEnum languageTypeEnum) {

        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<Field> collectionFieldList = entityLanguageStructure.getCollectionFieldList();
        Field concurrentCollectionField;
        for (int i = 0; i < collectionFieldList.size(); i++) {
            concurrentCollectionField = collectionFieldList.get(i);
            Collection collectionValue = (Collection) ClassUtils.getFieldValue(vo, concurrentCollectionField);
            if (null == collectionValue) {
                continue;
            }
            Iterator iterator = collectionValue.iterator();
            while (iterator.hasNext()) {
                convert(iterator.next(), languageTypeEnum);
            }

        }
    }

    private static <T> void convertMapField(T vo, LanguageTypeEnum languageTypeEnum) {

        EntityLanguageStructure entityLanguageStructure = entityLanguageStructureHashMap.get(vo.getClass().getCanonicalName());
        List<Field> mapFieldList = entityLanguageStructure.getMapFieldList();
        Field concurrentCollectionField;
        for (int i = 0; i < mapFieldList.size(); i++) {
            concurrentCollectionField = mapFieldList.get(i);
            Map<Object, Object> mapValue = (Map) ClassUtils.getFieldValue(vo, concurrentCollectionField);
            if (null == mapValue) {
                continue;
            }
            Iterator iterator = mapValue.entrySet().iterator();
            while (iterator.hasNext()) {
                convert(iterator.next(), languageTypeEnum);
            }

        }
    }

    private static void resolveClass(Class clazz) {
        String className = clazz.getCanonicalName();
        if (entityLanguageStructureHashMap.get(className) == null) {
            EntityLanguageStructure entityLanguageStructure = distillLanguageField(clazz);
            entityLanguageStructureHashMap.put(className, entityLanguageStructure);

        }
        if (entityLanguageFieldPairMap.get(className) == null) {
            Map<SupplementFieldEnum, List<FieldLanguageInfo>> supplementFieldMap = getSupplementFieldPairMap(entityLanguageStructureHashMap.get(className));
            entityLanguageFieldPairMap.put(className, supplementFieldMap);
        }
    }

    private static Map<SupplementFieldEnum, List<FieldLanguageInfo>> getSupplementFieldPairMap(EntityLanguageStructure entityLanguageStructure) {
        Map<SupplementFieldEnum, List<FieldLanguageInfo>> supplementFieldMap = new HashMap<>(128);
        List<FieldLanguageInfo> fieldLanguageInfoList = entityLanguageStructure.getPrimitiveFieldList();
        FieldLanguageInfo fieldLanguageInfo;
        SupplementFieldEnum currentSupplementFieldEnum;
        for (int i = 0; i < fieldLanguageInfoList.size(); i++) {
            fieldLanguageInfo = fieldLanguageInfoList.get(i);
            currentSupplementFieldEnum = fieldLanguageInfo.getSupplementFieldEnum();
            List<FieldLanguageInfo> tempFieldLanguageInfoList =
                    null == supplementFieldMap.get(currentSupplementFieldEnum) ?
                            new ArrayList<>() : supplementFieldMap.get(currentSupplementFieldEnum);
            tempFieldLanguageInfoList.add(fieldLanguageInfo);
            supplementFieldMap.putIfAbsent(fieldLanguageInfo.getSupplementFieldEnum(), tempFieldLanguageInfoList);
        }
        return supplementFieldMap;
    }

    public static List<Field> getAllFields(Class clazz) {
        List<Field> fieldList = new ArrayList<>();
        Class superClazz = clazz.getSuperclass();
        if (superClazz.isAnnotationPresent(LanguageEntityConvert.class)) {
            fieldList.addAll(getAllFields(superClazz));
        }
        Field[] fields = clazz.getDeclaredFields();
        if (null != fields && fields.length > 0) {
            for (int i = 0; i < fields.length; i++) {
                fieldList.add(fields[i]);
            }
        }
        return fieldList;
    }

    private static EntityLanguageStructure distillLanguageField(Class clazz) {

        List<Field> fields = getAllFields(clazz);
        List<FieldLanguageInfo> primitiveFieldList = new ArrayList<>();
        List<Field> referenceFieldList = new ArrayList<>();
        List<Field> collectionFieldList = new ArrayList<>();
        List<Field> mapFieldList = new ArrayList<>();
        Field field;
        for (int i = 0; i < fields.size(); i++) {
            field = fields.get(i);
            if (!field.isAnnotationPresent(LanguagePrimitiveConvert.class)
                    && !field.isAnnotationPresent(LanguageReferenceConvert.class)
                    && !field.isAnnotationPresent(LanguageCollectionConvert.class)) {
                continue;
            }
            field.setAccessible(true);
            if (field.isAnnotationPresent(LanguagePrimitiveConvert.class)) {
                LanguagePrimitiveConvert languagePrimitiveConvert = field.getAnnotation(LanguagePrimitiveConvert.class);
                LanguageTypeEnum languageTypeEnum = languagePrimitiveConvert.languageType();
                SupplementFieldEnum supplementFieldEnum = languagePrimitiveConvert.fieldType();

                FieldLanguageInfo fieldLanguageInfo = new FieldLanguageInfo(languageTypeEnum, supplementFieldEnum, field);
                primitiveFieldList.add(fieldLanguageInfo);

            }
            if (field.isAnnotationPresent(LanguageReferenceConvert.class)) {
                referenceFieldList.add(field);
            }

            if (field.isAnnotationPresent(LanguageCollectionConvert.class) && Collection.class.isAssignableFrom(field.getType())) {
                collectionFieldList.add(field);
            }

            if (field.isAnnotationPresent(LanguageMapConvert.class) && Map.class.isAssignableFrom(field.getType())) {
                mapFieldList.add(field);
            }
        }
        EntityLanguageStructure entityLanguageStructure = new EntityLanguageStructure();
        entityLanguageStructure.setPrimitiveFieldList(primitiveFieldList);
        entityLanguageStructure.setReferenceFieldList(referenceFieldList);
        entityLanguageStructure.setCollectionFieldList(collectionFieldList);
        entityLanguageStructure.setMapFieldList(mapFieldList);
        return entityLanguageStructure;
    }

    private static <T> boolean validate(T vo) {
        if (null == vo) {
            return false;
        }
        if (!vo.getClass().isAnnotationPresent(LanguageEntityConvert.class)) {
            return false;
        }
        return true;
    }

    public static boolean isEmpty(String value) {
        return value == null || value.length() == 0;
    }
}


